/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package br.ufscar.compiladores.compiler2;

import br.ufscar.compiladores.ast.AssignmentCommand;
import br.ufscar.compiladores.ast.*;
import br.ufscar.compiladores.lexer.Lexer;
import br.ufscar.compiladores.lexer.Symbol;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
/*
 *
 */

public class Compiler {
    
    private SymbolTable symbolTable;
    private Lexer lexer;
    private CompilerError error;
    // keeps a pointer to the current function being compiled
    private Function currentFunction;
    
    public Program compile(char[] input, PrintWriter outError) {
        symbolTable = new SymbolTable();
        error = new CompilerError(lexer, new PrintWriter(outError));
        lexer = new Lexer(input, error);
        error.setLexer(lexer);
        lexer.nextToken();
        Program p = null;
        try {
            p = program();
        } catch (Exception e) {
            e.printStackTrace();
        }
        if (error.wasAnErrorSignalled()) {
            return null;
        } else {
            return p;
        }
    }
    
    private Program program() {
        //Program ::= GlobalVarList ProcList
        Program program = new Program(GlobalLocalvarDec(true), procList());
        
        if (lexer.token != Symbol.EOF) {
            error.signal("EOF expected");
        }
        if (symbolTable.getInGlobal("main") == null) {
            //error.show("Source code must have a procedure called main");
        }
        return program;
    }
    
    private Collection<VarDecList> GlobalLocalvarDec(boolean isGlobal) {
        //GlobalVarList ::= [ VarDec { VarDec } ]
        Collection<VarDecList> globalLocalVarList = new ArrayList<VarDecList>();
        
        while (lexer.token == Symbol.INTEGER || lexer.token == Symbol.BOOLEAN) {
            varDec(globalLocalVarList, isGlobal);
        }
        return globalLocalVarList;
    }
    
    private void varDec(Collection<VarDecList> varList,boolean isGlobal) {
        // VarDec ::= Type Id { "," Id } ";"
        VarDecList varDecListToInclude = new VarDecList();

        // get the type
        Type typeVar = type();
        while (true) {
            if (lexer.token != Symbol.ID) {
                error.signal("Id expected");
            }
            
            String name = (String) lexer.getStringValue();
            lexer.nextToken();
            // semantic analysis
            // if the name is in the symbol table and the scope of the name is local,
            // the variable is been declared twice.
            if (isGlobal && symbolTable.getInGlobal(name) != null) {
                error.show("Global variable " + name + " has already been declared");
            }else if (!isGlobal && symbolTable.getInLocal(name) != null) {
                error.show("Local variable " + name + " has already been declared");
            }
            Variable v = new Variable(name, typeVar);
            // inserts the variable in the symbol table. The name is the key and an
            // object of class Variable is the value. Hash tables store a pair (key, value)
            // retrieved by the key.
            if (isGlobal) 
                symbolTable.putInGlobal(name, v);
            else
                symbolTable.putInLocal(name, v);
            // list of the last variables declared. They don't have types yet
            varDecListToInclude.add(v);
            if (lexer.token == Symbol.COMMA) {
                lexer.nextToken();
            } else {
                break;
            }
        }
        
        if (lexer.token != Symbol.SEMICOLON) {
            error.show("; expected");
            lexer.skipPunctuation();
        } else {
            lexer.nextToken();
        }
        
        varList.add(varDecListToInclude);
    }
    
    private Type type() {
        Type type;
        switch (lexer.token) {
            case Symbol.INTEGER:
                type = Type.integerType;
                break;
            case Symbol.BOOLEAN:
                type = Type.booleanType;
                break;
            default:
                error.show("Type expected");
                type = Type.integerType;
        }
        lexer.nextToken();
        return type;
    }
    
    private ProcedureDecList procList() {
        //ProcList ::= ProcDec { ProcDec }
        ProcedureDecList procedureDecList = new ProcedureDecList();
        
        while (lexer.token == Symbol.PROC) {
            procedureDecList.add(procDec());
        }
        
        return procedureDecList;
    }
    
    private Subroutine procDec() {
        //ProcDec ::= "proc" [ Type ] Id "(" [ ParamList ] ")" [ VarDec { VarDec } ] ProcBody
        boolean isFunction = false;
        if (lexer.token == Symbol.PROC) {
            lexer.nextToken();
        } else {
            error.signal("proc expected");
        }
        Type type = null;
        if (lexer.token == Symbol.INTEGER || lexer.token == Symbol.BOOLEAN) {
            type = type();
            isFunction = true;
        }
        
        if (lexer.token != Symbol.ID) {
            error.signal("Identifier expected");
        }
        String name = (String) lexer.getStringValue();
        // Symbol table now searches for an identifier in the scope order. First
        // the local variables and parameters and then the procedures and functions.
        // at this point, there should not be any local variables/parameters in the
        // symbol table.
        Subroutine subroutine = (Subroutine) symbolTable.getInGlobal(name);
        // semantic analysis
        // identifier is in the symbol table
        if (subroutine != null) {
            error.show("Procedure " + name + " has already been declared");
        }
        lexer.nextToken();
        if (isFunction) {
            // currentFunction is used to store the function being compiled 
            //or null if it is a procedure
            subroutine = currentFunction = new Function(name);
        } else {
            subroutine = new Procedure(name);
            currentFunction = null;
        }
        // insert s in the symbol table
        symbolTable.putInGlobal(name, subroutine);
        if (lexer.token != Symbol.LEFTPAR) {
            error.show("( expected");
            lexer.skipBraces();
        } else {
            lexer.nextToken();
        }
        // semantic analysis
        // if the subroutine is "main", it must be a parameterless procedure
        if (name.equalsIgnoreCase("main")) {
            error.show("subroutines caled main is not suported");
        }
        subroutine.setParamList(paramList());
        if (lexer.token != Symbol.RIGHTPAR) {
            error.show(") expected");
            lexer.skipBraces();
        } else {
            lexer.nextToken();
        }
        if (isFunction) {
            ((Function) subroutine).setReturnType(type);
        }
        
        subroutine.setLocalVarList(GlobalLocalvarDec(false));
        
        subroutine.setProcBody(procBody());
        symbolTable.removeLocalIdent();
        return subroutine;
    }
    
    private ParamList paramList() {
        //ParamList ::= Type Id {"," Type Id }
        ParamList paramList = null;
        
        paramList = new ParamList();
        if (lexer.token == Symbol.INTEGER || lexer.token == Symbol.BOOLEAN) {
            getParam(paramList);
            while (lexer.token == Symbol.COMMA) {
                lexer.nextToken();
                getParam(paramList);
            }
        }
        
        return paramList;
    }
    
    private void getParam(ParamList paramList) {
        //ParamList ::= Type Id {"," Type Id }
        if (lexer.token != Symbol.INTEGER && lexer.token != Symbol.BOOLEAN) {
            error.signal("Type expected");
        }
        Type type = type();        
        if (lexer.token != Symbol.ID) {
            error.signal("ID expected");
        }
        String name = (String) lexer.getStringValue();
        lexer.nextToken();
        if (symbolTable.getInLocal(name) != null) {
                error.show("Parameter " + name + " has already been declared");
        }
        Parameter v = new Parameter(name, type);
        symbolTable.putInLocal(name, v);
        paramList.addElement(v);
    }
    
    private ProcBody procBody() {
        //ProcBody ::= "begin" { Command } "end"
        if (lexer.token != Symbol.BEGIN) {
            error.show("begin expected");
        } else {
            lexer.nextToken();
        }
        ProcBody pb = new ProcBody(commandList());
        if (lexer.token != Symbol.END) {
            error.show("end expected");
            pb=null;
        } else {
            lexer.nextToken();
        }
        return pb;
    }

    private CommandList commandList() {
        //{ Command }
        int tk;
        Command command;
        Collection<Command> commands = new ArrayList<Command>();
        // statements always begin with an identifier, if, read or write, ...
        while ((tk = lexer.token) != Symbol.END && tk != Symbol.ELSE ) {
            command = null;
            try {
                // command() should return null in a serious error
                command = command();
            } catch (StatementException e) {
                lexer.skipToNextStatement();
            }
            if (command != null) {
                commands.add(command);
                
            }
        }
        return new CommandList(commands);
    }
    
    private Command command() throws StatementException{
        //Command ::= PrintCommand | AssignmentCommand | WhileCommand | ForCommand | IfCommand
        //          | CompositeCommand | ReturnCommand | ProcCall
        switch (lexer.token) {
            case Symbol.PRINT:
                return printCommand();
            case Symbol.ID:
                // if the identifier is in the symbol table, "symbolTable.get(...)"
                // will return the corresponding object. If it is a procedure,
                // we should call procedureCall(). Otherwise we have an assignment
                if (symbolTable.get(lexer.getStringValue()) instanceof Procedure) // Is the identifier a procedure?
                {
                    return procCall();
                } else {
                    return assignmentCommand();
                }    
            case Symbol.IF :
                return ifCommand();
            case Symbol.WHILE:
                return whileCommand();
            case Symbol.FOR :
                return forCommand();
            case Symbol.BEGIN :
                return compositeCommand();
            case Symbol.RETURN :
                return returnCommand();
            default:
                error.show("Statement expected");
                throw new StatementException();
        }
    }
    
    private PrintCommand printCommand() {
        //PrintCommand ::= "print" OrExpr ";"
        lexer.nextToken();
        // expression may be of any type
        Expr e = orExpr();
        if (lexer.token != Symbol.SEMICOLON) {
            error.show("; expected", true);
            lexer.skipPunctuation();
            return null;
        } else {
            lexer.nextToken();
            return new PrintCommand(e);
        }
    }

    private AssignmentCommand assignmentCommand() {
        //AssignmentCommand ::= Id "=" OrExpr ";"
        Variable v = getVariable();
        String name = v.getName();
        if (lexer.token != Symbol.ASSIGN) {
            error.show("= expected");
            lexer.skipToNextStatement();
            return null;
        } else {
            lexer.nextToken();
        }
        Expr right = orExpr();
        // semantic analysis
        // check if expression has the same type as variable
        if (!checkAssignment(v.getType(), right.getType())) {
            error.show("Type error in assignment");
        }
        if (lexer.token != Symbol.SEMICOLON) {
            error.show("; expected", true);
            lexer.skipPunctuation();
            return null;
        } else {
            lexer.nextToken();
            return new AssignmentCommand(v, right);
        }
    }
    
    private Variable getVariable() {
        // tests if the current identifier is a declared variable. If not,
        // declares it with the type Type.undefinedType.
        // assume lexer.token == Symbol.IDENT
        Variable v = null;
        String name = (String) lexer.getStringValue();
        try {
            v = (Variable) symbolTable.getInLocal(name);
        } catch (Exception e) {
            e.printStackTrace();
        }
        // semantic analysis
        // was the variable declared?
        if (v == null) {
            error.show("Variable " + name + " was not declared");
            v = new Variable(name, Type.undefinedType);
            symbolTable.putInLocal(name, v);
        }
        lexer.nextToken();
        return v;
    }

    private WhileCommand whileCommand() throws StatementException {
        //WhileCommand ::= "while" OrExpr "do" Command
        lexer.nextToken();
        Expr expr = orExpr();
        if (!checkWhileExpr(expr.getType())) {
            error.show("Boolean expression expected");
        }

        if (lexer.token != Symbol.DO) {
            error.show("do expected");
        } else {
            lexer.nextToken();
        }
        return new WhileCommand(expr, command());
    }
    
    private boolean checkWhileExpr(Type exprType) {
        if (exprType == Type.undefinedType || exprType == Type.booleanType) {
            return true;
        } else {
            return false;
        }
    }

    private ForCommand forCommand() throws StatementException {
        //ForCommand ::= "for" Id "=" OrExpr "to" OrExpr "do" Command
        Variable v = null;
        lexer.nextToken();
        if (lexer.token != Symbol.ID) {
            error.signal("Variable expected");
        }
        v = getVariable();
        // variable can be of any type
        if (lexer.token != Symbol.ASSIGN) {
            error.show("= expected");
        } else {
            lexer.nextToken();
        }
        Expr exprLower = orExpr();
        if (lexer.token != Symbol.TO) {
            error.show("to expected");
        } else {
            lexer.nextToken();
        }
        Expr exprUpper = orExpr();
        // semantic analysis
        if (!checkForExpr(exprLower.getType(), exprUpper.getType())) {
            error.show("both for expressions should have the same type");
        }
        if (lexer.token != Symbol.DO) {
            error.show("do expected");
        } else {
            lexer.nextToken();
        }
        return new ForCommand(v, exprLower, exprUpper, command());
    }
    
    private boolean checkForExpr(Type lowerExprType, Type upperExprType) {
        if (lowerExprType == Type.undefinedType || upperExprType == Type.integerType) {
            return true;
        } else {
            return lowerExprType == upperExprType;
        }
    }

    private IfCommand ifCommand() throws StatementException {
        //IfCommand ::= "if" OrExpr "then" Command [ "else" Command ]
        lexer.nextToken();
        Expr e = orExpr();
        // semantic analysis
        // check if expression has type boolean
        if (e.getType() != Type.booleanType) {
            error.show("Boolean type expected in if expression");
        }
        if (lexer.token != Symbol.THEN) {
            error.show("then expected");
        } else {
            lexer.nextToken();
        }
        Command thenPart = command();
        Command elsePart = null;
        if (lexer.token == Symbol.ELSE) {
            lexer.nextToken();
            elsePart = command();
        }
        return new IfCommand(e, thenPart, elsePart);
    }
    
    private CompositeCommand compositeCommand() {
        //CompositeCommand ::= "begin" { Command } "end"
        lexer.nextToken();
        CompositeCommand cc = new CompositeCommand(commandList());
        if (lexer.token != Symbol.END) {
            error.show("end expected");
            cc = null;
        } else {
            lexer.nextToken();
        }
        return cc;
    }

    private ReturnCommand returnCommand() {
        //ReturnCommand ::= "return" OrExpr ";"
        lexer.nextToken();
        Expr e = orExpr();
        // semantic analysis
        // Are we inside a function?
        if (currentFunction == null) {
            error.show("return statement inside a procedure");
        } else if (!checkAssignment(currentFunction.getReturnType(),
                e.getType())) {
            error.show("Return type does not match function type");
        }
        if (lexer.token != Symbol.SEMICOLON) {
            error.show("; expected", true);
            lexer.skipPunctuation();
            return null;
        } else {
            lexer.nextToken();
            return new ReturnCommand(e);
        }
    }

    private ProcedureCall procCall() {
        //ProcCall ::= Id "(" [ OrExpr { "," OrExpr } ] ")"
        // we already know the identifier is a procedure. So we need not to check it
        // again.
        ExprList anExprList = null;
        String name = (String) lexer.getStringValue();
        lexer.nextToken();
        Procedure p = (Procedure) symbolTable.getInGlobal(name);
        if (lexer.token != Symbol.LEFTPAR) {
            error.show("( expected");
            lexer.skipBraces();
        } else {
            lexer.nextToken();
        }
        if (lexer.token != Symbol.RIGHTPAR) {
            // The parameter list is used to check if the arguments to the
            // procedure have the correct types
            anExprList = exprList(p.getParamList());

            if (lexer.token != Symbol.RIGHTPAR) {
                error.show("Error in expression");
            } else {
                lexer.nextToken();
            }
        } else {
            // semantic analysis
            // does the procedure has no parameter?
            if (p.getParamList() != null && p.getParamList().getSize() != 0) {
                error.signal("Parameter expected");
            }
            lexer.nextToken();
        }
        return new ProcedureCall(p, anExprList);
    }

    private Expr orExpr() {
        //OrExpr ::= AndExpr [ "or" AndExpr ]
        Expr left, right;
        left = andExpr();
        if (lexer.token == Symbol.OR) {
            lexer.nextToken();
            right = andExpr();
            // semantic analysis
            if (!checkBooleanExpr(left.getType(), right.getType())) {
                error.show("Expression of boolean type expected");
            }
            left = new CompositeExpr(left, Symbol.OR, right);
        }
        return left;
    }

    private Expr andExpr() {
        //AndExpr ::= RelExpr [ "and" RelExpr ]
        Expr left, right;
        left = relExpr();
        if (lexer.token == Symbol.AND) {
            lexer.nextToken();
            right = relExpr();
            // semantic analysis
            if (!checkBooleanExpr(left.getType(), right.getType())) {
                error.show("Expression of boolean type expected");
            }
            left = new CompositeExpr(left, Symbol.AND, right);
        }
        return left;
    }

    private boolean checkBooleanExpr(Type left, Type right) {
        if (left == Type.undefinedType || right == Type.undefinedType) {
            return true;
        } else {
            return left == Type.booleanType && right == Type.booleanType;
        }
    }

    private Expr relExpr() {
        //RelExpr ::= AddExpr [ RelOp AddExpr ]
        Expr left, right;
        left = addExpr();
        int op = lexer.token;
        if (op == Symbol.EQ || op == Symbol.NEQ || op == Symbol.LE || op == Symbol.LT
                || op == Symbol.GE || op == Symbol.GT) {
            lexer.nextToken();
            right = addExpr();
            // semantic analysis
            if (!checkRelExpr(left.getType(), right.getType())) {
                error.show("Type error in expression");
            }
            left = new CompositeExpr(left, op, right);
        }
        return left;
    }

    private Expr addExpr() {
        //AddExpr ::= MultExpr { AddOp MultExpr }
        int op;
        Expr left, right;
        left = multExpr();
        while ((op = lexer.token) == Symbol.PLUS
                || op == Symbol.MINUS) {
            lexer.nextToken();
            right = multExpr();
            // semantic analysis
            if (!checkMathExpr(left.getType(), right.getType())) {
                error.show("Expression of type integer expected");
            }
            left = new CompositeExpr(left, op, right);
        }
        return left;
    }

    private boolean checkRelExpr(Type left, Type right) {
        if (left == Type.undefinedType || right == Type.undefinedType) {
            return true;
        } else {
            return left == right;
        }
    }

    private Expr multExpr() {
        //MultExpr ::= SimpleExpr { MultOp SimpleExpr }
        Expr left, right;
        left = simpleExpr();
        int op;
        while ((op = lexer.token) == Symbol.MULT
                || op == Symbol.DIV || op == Symbol.REMAINDER) {
            lexer.nextToken();
            right = simpleExpr();
            // semantic analysis
            if (!checkMathExpr(left.getType(), right.getType())) {
                error.show("Expression of type integer expected");
            }
            left = new CompositeExpr(left, op, right);
        }
        return left;
    }
    
    private boolean checkMathExpr(Type left, Type right) {
        boolean orLeft = left == Type.integerType
                || left == Type.undefinedType;
        boolean orRight = right == Type.integerType
                || right == Type.undefinedType;
        return orLeft && orRight;
    }

    private Expr simpleExpr() {
        /*
         * SimpleExpr ::= Number | "true" | "false" | ’(’ OrExpr ’)’ |
                        "not" SimpleExpr | AddOp SimpleExpr | Id | getInteger | ProcCall
         */
        Expr e;
        // note we test the lexer.getToken() to decide which production to use
        switch (lexer.token) {
            case Symbol.NUMBER:
                return number();
            case Symbol.TRUE:
                lexer.nextToken();
                return BooleanExpr.True;
            case Symbol.FALSE:
                lexer.nextToken();
                return BooleanExpr.False;
            case Symbol.LEFTPAR:
                lexer.nextToken();
                e = orExpr();
                if (lexer.token != Symbol.RIGHTPAR) {
                    error.show(") expected");
                    lexer.skipBraces();
                } else {
                    lexer.nextToken();
                }
                return new ParenthesisExpr(e);
            case Symbol.NOT:
                lexer.nextToken();
                e = orExpr();
                // semantic analysis
                if (e.getType() != Type.booleanType) {
                    error.show("Expression of type boolean expected");
                }
                return new UnaryExpr(e, Symbol.NOT);
            case Symbol.PLUS:
                lexer.nextToken();
                e = orExpr();
                // semantic analysis
                if (e.getType() != Type.integerType) {
                    error.show("Expression of type integer expected");
                }
                return new UnaryExpr(e, Symbol.PLUS);

            case Symbol.MINUS:
                lexer.nextToken();
                e = orExpr();
                // semantic analysis
                if (e.getType() != Type.integerType) {
                    error.show("Expression of type integer expected");
                }
                return new UnaryExpr(e, Symbol.MINUS);
            case Symbol.GETINTEGER:
                return new GetExpr(Type.integerType);
            default:
                // an identifier
                if (lexer.token != Symbol.ID) {
                    error.show("Identifier expected");
                    lexer.nextToken();
                    return new VariableExpr(new Variable("nameless", Type.undefinedType));
                } else {
                    // this part needs to be improved. If the compiler finds
                    // a call to a function that was not declared, it will sign an
                    // error "Identifier was not declared" and signal errors because of the
                    // parentheses following the function name. This can be corrected.
                    String name = (String) lexer.getStringValue();
                    // is it a function?
                    Object objIdent = symbolTable.get(name);
                    if (objIdent == null) {
                        error.show("Identifier was not declared");
                        lexer.nextToken();
                        if (lexer.token != Symbol.LEFTPAR) {
                            Variable newVariable = new Variable(name, Type.undefinedType);
                            symbolTable.putInLocal(name, newVariable);
                            return new VariableExpr(newVariable);
                        } else {
                            Function falseFunction = new Function(name);
                            falseFunction.setReturnType(Type.undefinedType);
                            falseFunction.setProcBody(null);
                            symbolTable.putInGlobal(name, falseFunction);
                            objIdent = falseFunction;
                        }
                    }
                    if (objIdent instanceof Subroutine) {
                        if (objIdent instanceof Function) {
                            return functionCall();
                        } else {
                            error.show("Attempt to call a procedure in an expression");
                            procedureCall();
                            return new VariableExpr(new Variable("nameless", Type.undefinedType));

                        }
                    } else {
                        // it is a variable
                        Variable v = (Variable) objIdent;
                        lexer.nextToken();
                        return new VariableExpr(v);
                    }
                }
        }
    }

    private NumberExpr number() {
        // the number value is stored in lexer.getToken().value as an object of Integer.
        // Method intValue returns that value as an value of type int.
        int value = lexer.getNumberValue();
        lexer.nextToken();
        return new NumberExpr(value);
    }

    private FunctionCall functionCall() {
        // we already know the identifier is a function. So we
        // need not to check it again.
        ExprList anExprList = null;
        String name = (String) lexer.getStringValue();
        lexer.nextToken();
        Function p = (Function) symbolTable.getInGlobal(name);
        if (lexer.token != Symbol.LEFTPAR) {
            error.show("( expected");
            lexer.skipBraces();
        } else {
            lexer.nextToken();
        }
        if (lexer.token != Symbol.RIGHTPAR) {
            // The parameter list is used to check if the arguments to the
            // procedure have the correct types
            anExprList = exprList(p.getParamList());
            if (lexer.token != Symbol.RIGHTPAR) {
                error.show("Error in expression");
            } else {
                lexer.nextToken();
            }
        } else {
            // semantic analysis
            // does the procedure has no parameter?
            if (p.getParamList() != null && p.getParamList().getSize() != 0) {
                error.show("Parameter expected");
            }
            lexer.nextToken();
        }
        return new FunctionCall(p, anExprList);
    }
    
    ExprList exprList(ParamList paramList) {
        ExprList anExprList;
        boolean firstErrorMessage = true;
        if (lexer.token == Symbol.RIGHTPAR) {
            return null;
        } else {
            //Parameter parameter;
            int sizeParamList = paramList.getSize();
            Enumeration e = paramList.elements();
            anExprList = new ExprList();
            for (Parameter parameter : paramList.getList()){
                // semantic analysis
                // does the procedure has one more parameter?
                if (sizeParamList < 1 && firstErrorMessage) {
                    error.show("Wrong number of parameters in call");
                    firstErrorMessage = false;
                }
                sizeParamList--;
                Expr anExpr = orExpr();
                if (!checkAssignment(parameter.getType(), anExpr.getType())) {
                    error.show("Type error in parameter passing");
                }
                anExprList.addElement(anExpr);
                if (lexer.token == Symbol.COMMA) {
                    lexer.nextToken();
                } else {
                    break;
                }
            }
            // semantic analysis
            // the procedure may need more parameters
            if (sizeParamList > 0 && firstErrorMessage) {
                error.show("Wrong number of parameters");
            }
            return anExprList;
        }
    }
    
    private boolean checkAssignment(Type varType, Type exprType) {
        if (varType == Type.undefinedType || exprType == Type.undefinedType) {
            return true;
        } else {
            return varType == exprType;
        }
    }

    private void procedureCall() {
        
    }

    
}
